/**
 * // Copyright (c) 2023 KylinOS. All rights reserved.
 * //
 * // This program is free software; you can redistribute it
 * // and/or modify it under the terms of the GNU General Public
 * // License (version 2) as published by the FSF - Free Software
 * // Foundation*
 */

/**
 * // Copyright (C) 2023 ben LG <guoliben@kylinos.cn>
 */

package compressextractor

import (
	"archive/zip"
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"time"

	"gitee.com/guoliben/kydlp-doctotext/3rdparty/fileidentify"
	"gitee.com/guoliben/kydlp-doctotext/compressextractor/fileattrfilter"
	"gitee.com/guoliben/kydlp-doctotext/docextractor"
	"gitee.com/guoliben/kydlp-doctotext/extractor_interface"
	"gitee.com/guoliben/kydlp-doctotext/imageextractor"
	"gitee.com/guoliben/kydlp-doctotext/utils/ctrlledprint"
)

type CompressZip struct{}

func (extractor CompressZip) GetTextFromFile(path string, size int64, filetype string, depth int) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(file)文档...")

	return zipGetTextFromFile(path, size, filetype, depth)
}

func (extractor CompressZip) GetTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) ([]byte, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(buffer)文档...")

	return zipGetTextFromBuffer(path, data, size, filetype, depth)
}

func (extractor CompressZip) GetStringTextFromFile(path string, size int64, filetype string, depth int) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(file)文档...")

	return zipGetStringTextFromFile(path, size, filetype, depth)
}

func (extractor CompressZip) GetStringTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) (string, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(buffer)文档...")

	return zipGetStringTextFromBuffer(path, data, size, filetype, depth)
}

func (extractor CompressZip) GetTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(file)文档...")

	return zipGetTextFromFileWithCallback(path, size, filetype, depth, callback, context)
}

func (extractor CompressZip) GetTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(buffer)文档...")

	return zipGetTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)
}

func (extractor CompressZip) GetStringTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(file)文档...")

	return zipGetStringTextFromFileWithCallback(path, size, filetype, depth, callback, context)
}

func (extractor CompressZip) GetStringTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	ctrlledprint.Println("提取文本: 正在解析zip(buffer)文档...")

	return zipGetStringTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)
}

func zipGetTextFromFile(path string, size int64, filetype string, depth int) ([]byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return nil, ""
	}
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return nil, ""
	}

	start := time.Now()

	reader, err := zip.OpenReader(path)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return nil, ""
	}

	var fileReader io.ReadCloser
	defer func() {
		if reader != nil {
			reader.Close()
		}
		if fileReader != nil {
			fileReader.Close()
		}
	}()

	var text bytes.Buffer

	for _, file := range reader.File {
		if file == nil || file.FileInfo() == nil || file.FileInfo().IsDir() {
			continue
		}

		fileReader, err = file.Open()
		if err != nil {
			ctrlledprint.Println("Error opening of sub file:", err)
			continue
		}

		content, err := ioutil.ReadAll(fileReader)
		if err != nil {
			ctrlledprint.Println("Error reading content of sub file:", err)
			continue
		}
		length := int64(len(content))
		if length <= 0 {
			ctrlledprint.Println("No content readed from sub file.")
			continue
		}

		// 调用指定类型文件解析器解析文件内容
		// -------------------------------------------------------------------------------------------------------
		filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

		switch strings.TrimSpace(filetype) {
		case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
			fileidentify.DocTypePdf,
			fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
			fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
			fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
			{
				if result, _ := docextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.Write(result)
				}
			}
		case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
			fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
			fileidentify.CompressType7z:
			{
				if result, _ := TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.Write(result)
				}
			}
		case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
			fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
			{
				if result, _ := imageextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.Write(result)
				}
			}
		}
		// -------------------------------------------------------------------------------------------------------
	}

	elapsed := time.Since(start)

	return text.Bytes(), elapsed.String()
}

func zipGetTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) ([]byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return nil, ""
	}
	size = int64(len(data))
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return nil, ""
	}

	start := time.Now()

	reader, err := zip.NewReader(bytes.NewReader(data), size)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return nil, ""
	}

	var fileReader io.ReadCloser
	defer func() {
		if reader != nil {
			reader = nil
		}
		if fileReader != nil {
			fileReader.Close()
		}
	}()

	var text bytes.Buffer

	for _, file := range reader.File {
		if file == nil || file.FileInfo() == nil || file.FileInfo().IsDir() {
			continue
		}

		fileReader, err = file.Open()
		if err != nil {
			ctrlledprint.Println("Error opening of sub file:", err)
			continue
		}

		content, err := ioutil.ReadAll(fileReader)
		if err != nil {
			ctrlledprint.Println("Error reading content of sub file:", err)
			continue
		}
		length := int64(len(content))
		if length <= 0 {
			ctrlledprint.Println("No content readed from sub file.")
			continue
		}

		// 调用指定类型文件解析器解析文件内容
		// -------------------------------------------------------------------------------------------------------
		filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

		switch strings.TrimSpace(filetype) {
		case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
			fileidentify.DocTypePdf,
			fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
			fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
			fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
			{
				if result, _ := docextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.Write(result)
				}
			}
		case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
			fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
			fileidentify.CompressType7z:
			{
				if result, _ := TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.Write(result)
				}
			}
		case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
			fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
			{
				if result, _ := imageextractor.TextExtractorFactory(filetype).GetTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.Write(result)
				}
			}
		}
		// -------------------------------------------------------------------------------------------------------
	}

	elapsed := time.Since(start)

	return text.Bytes(), elapsed.String()
}

func zipGetStringTextFromFile(path string, size int64, filetype string, depth int) (string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return "", ""
	}
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return "", ""
	}

	start := time.Now()

	reader, err := zip.OpenReader(path)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return "", ""
	}

	var fileReader io.ReadCloser
	defer func() {
		if reader != nil {
			reader.Close()
		}
		if fileReader != nil {
			fileReader.Close()
		}
	}()

	var text bytes.Buffer

	for _, file := range reader.File {
		if file == nil || file.FileInfo() == nil || file.FileInfo().IsDir() {
			continue
		}

		fileReader, err = file.Open()
		if err != nil {
			ctrlledprint.Println("Error opening of sub file:", err)
			continue
		}

		content, err := ioutil.ReadAll(fileReader)
		if err != nil {
			ctrlledprint.Println("Error reading content of sub file:", err)
			continue
		}
		length := int64(len(content))
		if length <= 0 {
			ctrlledprint.Println("No content readed from sub file.")
			continue
		}

		// 调用指定类型文件解析器解析文件内容
		// -------------------------------------------------------------------------------------------------------
		filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

		switch strings.TrimSpace(filetype) {
		case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
			fileidentify.DocTypePdf,
			fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
			fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
			fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
			{
				if result, _ := docextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.WriteString(result)
				}
			}
		case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
			fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
			fileidentify.CompressType7z:
			{
				if result, _ := TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.WriteString(result)
				}
			}
		case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
			fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
			{
				if result, _ := imageextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.WriteString(result)
				}
			}
		}
		// -------------------------------------------------------------------------------------------------------
	}

	elapsed := time.Since(start)

	return text.String(), elapsed.String()
}

func zipGetStringTextFromBuffer(path string, data []byte, size int64, filetype string, depth int) (string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return "", ""
	}
	size = int64(len(data))
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return "", ""
	}

	start := time.Now()

	reader, err := zip.NewReader(bytes.NewReader(data), size)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return "", ""
	}

	var fileReader io.ReadCloser
	defer func() {
		if reader != nil {
			reader = nil
		}
		if fileReader != nil {
			fileReader.Close()
		}
	}()

	var text bytes.Buffer

	for _, file := range reader.File {
		if file == nil || file.FileInfo() == nil || file.FileInfo().IsDir() {
			continue
		}

		fileReader, err = file.Open()
		if err != nil {
			ctrlledprint.Println("Error opening of sub file:", err)
			continue
		}

		content, err := ioutil.ReadAll(fileReader)
		if err != nil {
			ctrlledprint.Println("Error reading content of sub file:", err)
			continue
		}
		length := int64(len(content))
		if length <= 0 {
			ctrlledprint.Println("No content readed from sub file.")
			continue
		}

		// 调用指定类型文件解析器解析文件内容
		// -------------------------------------------------------------------------------------------------------
		filetype, _, _, _ := fileidentify.IdentifyFromBuffer(content)

		switch strings.TrimSpace(filetype) {
		case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
			fileidentify.DocTypePdf,
			fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
			fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
			fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
			{
				if result, _ := docextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.WriteString(result)
				}
			}
		case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
			fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
			fileidentify.CompressType7z:
			{
				if result, _ := TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.WriteString(result)
				}
			}
		case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
			fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
			{
				if result, _ := imageextractor.TextExtractorFactory(filetype).GetStringTextFromBuffer(path, content, length, filetype, depth); len(result) > 0 {
					text.WriteString(result)
				}
			}
		}
		// -------------------------------------------------------------------------------------------------------
	}

	elapsed := time.Since(start)

	return text.String(), elapsed.String()
}

func zipGetTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return 0, nil, ""
	}

	file, err := os.Open(path)
	if err != nil {
		ctrlledprint.Println("Error opening zip file:", err)
		return 0, nil, ""
	}
	defer file.Close()

	stat, err := file.Stat()
	if err != nil {
		ctrlledprint.Println("Error opening zip filepath:", err)
		return 0, nil, ""
	}
	size = stat.Size()
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return 0, nil, ""
	}

	reader, err := zip.NewReader(file, size)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return 0, nil, ""
	}

	return zipGetText(path, reader, size, filetype, depth, callback, context)
}

func zipGetTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return 0, nil, ""
	}
	size = int64(len(data))
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return 0, nil, ""
	}

	reader, err := zip.NewReader(bytes.NewReader(data), size)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return 0, nil, ""
	}

	return zipGetText(path, reader, size, filetype, depth, callback, context)
}

func zipGetStringTextFromFileWithCallback(path string, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return 0, "", ""
	}

	file, err := os.Open(path)
	if err != nil {
		ctrlledprint.Println("Error opening zip file:", err)
		return 0, "", ""
	}
	defer file.Close()

	stat, err := file.Stat()
	if err != nil {
		ctrlledprint.Println("Error opening zip filepath:", err)
		return 0, "", ""
	}
	size = stat.Size()
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return 0, "", ""
	}

	reader, err := zip.NewReader(file, size)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return 0, "", ""
	}

	return zipGetStringText(path, reader, size, filetype, depth, callback, context)
}

func zipGetStringTextFromBufferWithCallback(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	if depth > maxDetectDepth {
		ctrlledprint.Println("解析zip文档，达到最大解压层数:", maxDetectDepth)
		return 0, "", ""
	}
	size = int64(len(data))
	if uint32(size) >= MAX_COMPRESS_LIMIT {
		ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
		return 0, "", ""
	}

	reader, err := zip.NewReader(bytes.NewReader(data), size)
	if err != nil {
		ctrlledprint.Println("Error creating zip reader:", err)
		return 0, "", ""
	}

	return zipGetStringText(path, reader, size, filetype, depth, callback, context)
}

// zipGetText .zip格式文件统一解析入口
func zipGetText(path string, reader *zip.Reader, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	var retcode int
	var totaltext bytes.Buffer
	var totalelapsed time.Duration

	var fileReader io.ReadCloser
	defer func() {
		if fileReader != nil {
			fileReader.Close()
		}
	}()

	start := time.Now()

	for _, file := range reader.File {
		if file == nil || file.FileInfo() == nil || file.FileInfo().IsDir() {
			continue
		}

		subfilename := file.Name
		subfilesize := file.UncompressedSize64
		if uint32(subfilesize) >= MAX_COMPRESS_LIMIT {
			ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
			continue
		}
		if fileattrfilter.FilterExcludeFilepath(subfilename) {
			continue
		}
		if fileattrfilter.FilterExcludeFilesize("", int64(subfilesize)) {
			continue
		}

		fileReader, err := file.Open()
		if err != nil {
			ctrlledprint.Println("Error opening of sub file:", err)
			continue
		}

		retcode = ExtractTextWithCallback(path, fileReader, depth, callback, context)

		if retcode == 0 {
			continue
		} else {
			break
		}
	}

	elapsed := time.Since(start)

	totalelapsed += elapsed

	return retcode, totaltext.Bytes(), totalelapsed.String()
}

func zipGetStringText(path string, reader *zip.Reader, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	var retcode int
	var totaltext bytes.Buffer
	var totalelapsed time.Duration

	var fileReader io.ReadCloser
	defer func() {
		if fileReader != nil {
			fileReader.Close()
		}
	}()

	start := time.Now()

	for _, file := range reader.File {
		if file == nil || file.FileInfo() == nil || file.FileInfo().IsDir() {
			continue
		}

		subfilename := file.Name
		subfilesize := file.UncompressedSize64
		if uint32(subfilesize) >= MAX_COMPRESS_LIMIT {
			ctrlledprint.Println(path, "Zip file is larger then limit", MAX_COMPRESS_LIMIT)
			continue
		}
		if fileattrfilter.FilterExcludeFilepath(subfilename) {
			continue
		}
		if fileattrfilter.FilterExcludeFilesize("", int64(subfilesize)) {
			continue
		}

		fileReader, err := file.Open()
		if err != nil {
			ctrlledprint.Println("Error opening of sub file:", err)
			continue
		}

		retcode = ExtractStringTextWithCallback(path, fileReader, depth, callback, context)

		if retcode == 0 {
			continue
		} else {
			break
		}
	}

	elapsed := time.Since(start)

	totalelapsed += elapsed

	return retcode, totaltext.String(), totalelapsed.String()
}
